.. _Enumeration:

Enum
====

enum - это функциональность позволяющая создавать именованные константы

.. index::
    enum

Влияние магических констант на качество кода
--------------------------------------------

Следующий код появился в решениях упражнений (:ref:`Арифметические операции с целыми числами<Арифметические операции с целыми числами>`) главы "Целые
числа и арифметические операции":

.. code-block:: d

    if (operation == 1)
    {
        result = first + second;
    }
    else if (operation == 2)
    {
        result = first - second;
    }
    else if (operation == 3)
    {
        result = first * second;
    }
    else if (operation == 4)
    {
        result = first / second;
    }

Целочисленные литералы 1, 2, 3 и 4 в этом фрагменте кода называются магическими константами. Нелегко
определить, что означает каждый из этих литералов в программе.
Необходимо изучить код в каждой области, чтобы определить, что 1 означает сложение, 2
означает вычитание и т.д. Эта задача относительно проста для приведенного выше кода, поскольку все
области содержат всего одну строку. Было бы значительно сложнее
расшифровать значения магических констант в большинстве других программ.
Следует избегать магических констант, поскольку они ухудшают два важных качества
из исходного кода: читаемость и возможность поддержки.
enum позволяет присваивать имена таким константам и, как следствие, делать
код более читабельным и поддерживаемым Каждое условие было бы легко
понятно, если бы использовались следующие константы enum:

.. code-block:: d

    if (operation == Operation.add)
    {
        result = first + second;
    }
    else if (operation == Operation.subtract)
    {
        result = first - second;
    }
    else if (operation == Operation.multiply)
    {
        result = first * second;
    }
    else if (operation == Operation.divide)
    {
        result = first / second;
    }


Описанная выше операция типа enum, которая устраняет необходимость в магических константах 1, 2, 3
и 4, может быть определена следующим образом:

.. code-block:: d

    enum Operation { add = 1, subtract, multiply, divide }

Синтаксис enum
--------------

Наиболее общее определение enum(перечисления) является следующее

.. code-block:: d

    enum TypeName { ValueName_1, ValueName_2, /* etc. */ }

Иногда необходимо также указать базовый тип значений:

.. code-block:: d

    enum TypeName : base_type { ValueName_1, ValueName_2, /* etc. */ }

Мы увидим, как это используется в следующем разделе.
TypeName определяет, что означают константы в совокупности. Все
константы-члены типа enum перечислены в фигурных скобках. Вот несколько
примеров:

.. code-block:: d

    enum HeadsOrTails { heads, tails }
    enum Suit { spades, hearts, diamonds, clubs }
    enum Fare { regular, child, student, senior }

Каждый набор констант становится частью отдельного типа. Например, heads и
tails становятся членами типа HeadsOrTails. Новый тип можно использовать, как
и другие фундаментальные типы, при определении переменных:

.. code-block:: d

    HeadsOrTails result; // инициализация по умолчанию
    auto ht = HeadsOrTails.heads; // inferred type ????

Как было видно из приведенных выше фрагментов кода, члены типов enum
всегда указываются по имени их типа enum:

.. code-block:: d

    if (result == HeadsOrTails.heads)
    {
        // ...
    }

Фактические значения и базовые типы
-----------------------------------

.. index::
    фактические значения
    базовые типы

Константы-члены типов enum по умолчанию реализованы как значения int.
Другими словами, хотя внешне они выглядят просто как имена, такие как орел
и решка, они также имеют числовые значения.
*(Примечание: При необходимости можно выбрать тип , отличный от int.).*
Если программист явно не указал это, числовые значения элементов enum
начинаются с 0 и увеличиваются на единицу для каждого элемента. Например,
два элемента перечисления HeadsOrTails имеют числовые значения 0 и 1:

.. code-block:: d

    writeln("heads is 0: ", (HeadsOrTails.heads == 0));
    writeln("tails is 1: ", (HeadsOrTails.tails == 1));

.. code-block:: console

    heads is 0: true
    tails is 1: true

Можно вручную повторно установить значения в любой точке перечисления. Так было,
когда мы указали значение Operation.add как 1 выше. В следующем
примере вручную дважды устанавливается новое значение count:

.. code-block:: d

    enum Test { a, b, c, d = 100, d, e, f = 222, g, d }
    writefln("%d %d %d", Test.b, Test.d, Test.d);

Вывод

.. code-block:: console

    1 100 224

Если int не подходит в качестве базового типа значений перечисления, базовый тип может быть
указан явно после имени перечисления:

.. code-block:: d

    enum NaturalConstant : double { pi = 3.14, e = 2.72 }
    enum TemperatureUnit : string { C = "Celsius", F = "Fahrenheit" }

Значения, которые не относятся к типу enum
------------------------------------------

Мы обсуждали, что важно избегать магических констант и вместо
этого использовать перечислимые типы данных.

Однако иногда может быть неестественно придумывать имена типа enum
просто для использования именованных констант. Давайте предположим, что именованная константа необходима для
представления количества секунд в день. Необязательно также определять
тип перечисления, содержащий это постоянное значение. Все, что требуется, - это постоянное значение,
на которое можно ссылаться по его имени. В таких случаях имя типа перечисления и
фигурные скобки опускаются:

.. code-block:: d

    enum secondsPerDay = 60 * 60 * 24;

Тип константы может быть указан явно, если тип не может быть выведен.

.. code-block:: d

    enum int secondsPerDay = 60 * 60 * 24;

Поскольку нет реального перечисления (enum), на который можно ссылаться, такие именованные константы могут использоваться в коде
просто по их именам:

.. code-block:: d

    totalSeconds = totalDays * secondsPerDay;

enum может использоваться и для определения именованных констант других типов.
Например, типом следующей константы будет string:

.. code-block:: d

    enum fileName = "list.txt";

Такими константами являются значения (:ref:`Lvalues и Rvalues<Lvalues и Rvalues>`), и они называются манифестными константами.
Также возможно создавать манифестные константы массивов и ассоциативных массивов.
Однако, как мы увидим позже в главе о неизменяемости (:ref:`Неизменняемость<Неизменняемость>`),
массивы перечислений и ассоциативные массивы могут иметь скрытые издержки.

Свойства
--------

.. index::
    enum properties

Свойства .min и .max являются минимальными и максимальными значениями типа enum.
Когда значения типа enum являются последовательными, их можно перебирать
в цикле for в пределах этих ограничений:

.. code-block:: d

    {
        writefln("%s: %d", suit, suit);
    }

Обратите внимание, какие данные выдают спецификаторы формата "%s" и "%d"

.. code-block:: console

    spades: 0
    hearts: 1
    diamonds: 2
    clubs: 3

Обратите внимание, что цикл foreach в этом диапазоне исключил бы значение .max из
итерации:

.. code-block:: d

    foreach (suit; Suit.min .. Suit.max)
    {
        writefln("%s: %d", suit, suit);
    }

Вывод

.. code-block:: console

    spades: 0
    hearts: 1
    diamonds: 2
    clubs пропущено

По этой причине правильным способом перебора всех значений перечисления является использование шаблона
Enum Members из модуля std.traits:

.. code-block:: d

    import std.traits;
    // ...
    foreach (suit; EnumMembers!Suit)
    {
        writefln("%s: %d", suit, suit);
    }

*Примечание: Символ ! выше предназначен для создания экземпляров шаблона, которые будут рассмотрены в
следующих главах* (:ref:`Шаблоны<глава_64_start>`).

.. code-block:: console

    spades: 0
    hearts: 1
    diamonds: 2
    clubs: 3 ← clubs напечатано

Преобразование из базового типа
-------------------------------

Как мы видели в приведенных выше форматированных выходных данных, значение enum может быть автоматически
преобразовано в его базовый тип (например, в int). Обратное преобразование не является автоматическим:

.. code-block:: d

    Suit suit = 1; // ← ошибка компиляции

Одна из причин этого заключается в том, чтобы избежать получения недопустимых значений перечисления:

.. code-block:: d

    suit = 100; // может оказаться недопустимым значением

Значения, которые, как известно, соответствуют допустимым значениям перечисления определенного
типа перечисления, все еще могут быть преобразованы в этот тип с помощью явного приведения типа:

.. code-block:: d

    suit = cast(Suit)1; // допустимо

Ответственность программиста заключается в обеспечении достоверности значений
при использовании явного приведения. Мы рассмотрим приведение типов в следующей главе (:ref:`Преобразования типов<глава_46_start>`)

Упражнение
----------

Измените программу калькулятора из раздела Упражнения с целыми числами и арифметикой
Раздел Операции (:ref:`Целочисленная арифметика<Целочисленная арифметика>`), чтобы пользователь мог выбрать арифметическую операцию из
меню.
Эта программа должна отличаться от предыдущей, по крайней мере, в
следующих областях:

* Используйте перечислимые значения, а не магические константы.
* Используйте double вместо int.
* Используйте оператор switch вместо цепочки "if, else if, else".

